﻿#define PREBUILT
#define OLD

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Diagnostics;

namespace Mathematics.Parser
{
    public static class Lexer
    {
        #region Constants

        
        
        //private static readonly LinkedList<TokenDefinition> _tokenDefs = new LinkedList<TokenDefinition>();
        private static readonly List<TokenDefinition> _tokenDefs = new List<TokenDefinition>();

        public static readonly string VARIABLE_USE = @"[a-zA-Z][a-zA-Z0-9]*";
        public static readonly string NUMERIC_CONSTANT = @"[+-]?([0-9]*)(\.([0-9]*))?";
        public static readonly string STRING_CONSTANT = @"\'(.*)\'";
        public static readonly string ASSIGNMENT = @"(\s*)[=](\s*)";

        public static readonly string VARIABLE_ASSIGNMENT=VARIABLE_USE+ASSIGNMENT;

        public static readonly string GROUP_BEGIN = @"[\(]";
        public static readonly string GROUP_END = @"[\)]";
        public static readonly string DOMAIN_BEGIN = @"[\{]";
        public static readonly string DOMAIN_END = @"[\}]";
        public static readonly string NAMED_DOMAIN_BEGIN = VARIABLE_ASSIGNMENT + DOMAIN_BEGIN;
        

        public static readonly string PARAMETER_DEF = @"((" + VARIABLE_USE + ")|(" + VARIABLE_USE + ASSIGNMENT + "))";
        public static readonly string FUNCTION_DEF_SUFFIX = @"\((" + PARAMETER_DEF + @"(\,)(\s*))*(" + PARAMETER_DEF + @")?\)";
        public static readonly string FUNCTION_DEFINITION = VARIABLE_USE + FUNCTION_DEF_SUFFIX + ASSIGNMENT;

        public static readonly string FUNCTION_CALL_SUFFIX = @"\(.*\)";
        public static readonly string FUNCTION_CALL = VARIABLE_USE + FUNCTION_CALL_SUFFIX;

        public static readonly string IMPORT_KEYWORD = @"(^import)\s*";
        public static readonly string PRINT_KEYWORD = @"(^print)\s*";
        public static readonly string PRINTF_KEYWORD = @"(^printf)\s*";
        public static readonly string TRUE_KEYWORD = @"[\s]*(true)\s*";
        public static readonly string FALSE_KEYWORD = @"[\s]*(false)\s*";
        public static readonly string VOID_KEYWORD = @"[\s]*(void)\s*";

        public static readonly Regex VARIABLE_MATCH = new Regex(VARIABLE_USE);
        public static readonly Regex FUNCTION_DEF_MATCH = new Regex(VARIABLE_USE + FUNCTION_DEF_SUFFIX);
        public static readonly Regex FUNCTION_SUFFIX_MATCH = new Regex(FUNCTION_DEF_SUFFIX);

        #endregion

        #region Local Members

        private static bool _isCompiled;
        private static Regex _compiledExpression;

        #endregion

        #region Construction Methods

        static Lexer()
        {
            CreateBuiltInTokens();
            LoadTokenFiles();
        }

        private static void CreateBuiltInTokens()
        {
            // Capture and exclude comments
            //Add(TokenType.Comment, @"\/\/.*");
            Add(TokenType.LineTerminator, @"\n");
            Add(TokenType.DomainBegin, DOMAIN_BEGIN);
            Add(TokenType.NamedDomainBegin, NAMED_DOMAIN_BEGIN);
            Add(TokenType.DomainEnd,DOMAIN_END);

            // Keywords
            Add(TokenType.ImportKeyword, IMPORT_KEYWORD);
            Add(TokenType.PrintKeyword, PRINT_KEYWORD);
            Add(TokenType.PrintFKeyword, PRINTF_KEYWORD);
            Add(TokenType.TrueConstant, TRUE_KEYWORD);
            Add(TokenType.FalseConstant, FALSE_KEYWORD);
            Add(TokenType.VoidConstant, VOID_KEYWORD);

            // Functions
            Add(TokenType.FunctionAssignment, FUNCTION_DEFINITION);
            Add(TokenType.FunctionCall, FUNCTION_CALL);

            // Check for String Literals
            Add(TokenType.StringConstant, STRING_CONSTANT);

            // Variables
            Add(TokenType.VariableAssignment, VARIABLE_ASSIGNMENT);
            Add(TokenType.VariableUsage, VARIABLE_USE);

            // Groupings
            Add(TokenType.GroupingBegin, GROUP_BEGIN);
            Add(TokenType.GroupingEnd, GROUP_END);

            // Operators
            Add(TokenType.ExponentOperator, @"\^");
            Add(TokenType.MultiplyOperator, @"\*");
            Add(TokenType.DivideOperator, @"\/");
            Add(TokenType.ModOperator, @"\%");
            Add(TokenType.AddOperator, @"\+");
            Add(TokenType.SubtractOperator, @"\-");

            // Constant Definitions
            Add(TokenType.NumericConstant, NUMERIC_CONSTANT);

            // Layout (Whitespace) Definitions
            Add(TokenType.Whitespace, @"\s+");
            Add(TokenType.Anything, @"\.*");
        }

        private static void LoadTokenFiles()
        {
            // Do nothing for now;
        }

        #endregion

        #region Definition Methods

        internal static TokenDefinition Add(TokenType type,string symbol)
        {
            var newTokenDef = new TokenDefinition(type, symbol);
            //_tokenDefs.AddLast(newTokenDef);
            _tokenDefs.Add(newTokenDef);
            _isCompiled = false;
            return newTokenDef;
        }

        public static void ClearUserDefinitions()
        {
            _tokenDefs.Clear();
            CreateBuiltInTokens();
            LoadTokenFiles();
        }

        private static void Compile()
        {
            const string pattern = "({0})|";
            var builder = new StringBuilder();
            foreach (var def in _tokenDefs)
            {
                builder.AppendFormat(pattern,def.TokenSymbol);
            }
            builder.Remove(builder.Length - 1, 1);

            _compiledExpression = new Regex(builder.ToString());
            _isCompiled = true;
        }

        private static void CheckCompile()
        {
            if (!_isCompiled)
                Compile();
        }

        #endregion

        #region Lexical Methods

        public static IEnumerable<TokenString> LexText(string raw)
        {
            var lines = raw.Split('\n');
            foreach (var line in lines)
            {
                if (!string.IsNullOrWhiteSpace(line))
                    yield return LexLine(line);
            }
        }

        public static IEnumerable<TokenString> LexFile(string filePath)
        {
            var fileContent = File.ReadAllLines(filePath);
            foreach (var line in fileContent)
            {
                if (!string.IsNullOrWhiteSpace(line))
                    yield return LexLine(line);
            }
        }

        public static TokenString LexLine(string raw)
        {
            
#if OLD
            CheckCompile();
            var matches = _compiledExpression.Matches(raw);
            var tokens = new TokenString();
            
            foreach (Match match in matches)
            {
                
                foreach (Capture capture in match.Captures)
                {
                    var value = capture.Value;
                    if (!string.IsNullOrWhiteSpace(value))
                    {
                        var def = _tokenDefs.First(d => d.IsMatch(value));

                        if (def.Type == TokenType.Anything)
                            throw new LexicalException(string.Format("Unrecognized character string detected: {0}", value));

                        tokens.AddLast(new Token(def.Type, value.Trim()));
                        break;
                    }
                }
            }
            return tokens;
#else
            var tokens = new TokenString();
            foreach (var def in _tokenDefs)
            {
                var match = def.Expression.Match(raw);
                if (match.Success)
                {
                    var value = match.Value;
                    if (!string.IsNullOrWhiteSpace(value))
                    {
                        tokens.AddLast(new Token(def.Type, value));
                        raw = raw.Replace(value, string.Empty).Trim();
                    }
                }
            }
            return tokens;
#endif
        }


        #endregion
    }
}
